﻿namespace FallingWordsGame
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Media;
    using System.Threading;

    using FallingWordsGame.Interfaces;

    public class Engine
    {
        private const string BossFightSoundPath_Test = @"..\..\Sounds\BossFight.wav";
        private const string KeyStrokeSoundPath_Test = @"..\..\Sounds\KeyStroke.wav";
        private const string KeyStrokeSoundPath_Compile = "KeyStroke.wav";
        private const string BossFightSoundPath_Compile = "BossFigth.wav";

        private const string BossTextFilePath_Test = @"..\..\TextFiles\Boss.txt";
        private const string WordTextFilePath_Test = @"..\..\TextFiles\WordsDictionary.txt";
        private const string BossTextFilePath_Compile = "Boss.txt";
        private const string WordTextFilePath_Compile = "WordsDictionary.txt";

        private readonly SoundPlayer keyStrokeSound;
        private readonly SoundPlayer bossFightSound;

        private readonly Renderer renderer;
        private readonly Keyboard keyboard;

        private readonly List<GameObject> activeObjects;
        private readonly IList<IList<IWord>> bossBlocksDataBase;

        private readonly WordGenerator generator;

        private readonly int height;
        private readonly int width;

        private GameObject selectedObject;

        private bool inRefreshingSemaphore;

        public Engine(Keyboard keyBoard, int height, int width)
        {
            this.keyboard = keyBoard;
            this.height = height;
            this.width = width;

            this.bossBlocksDataBase = new List<IList<IWord>>();
            this.activeObjects = new List<GameObject>();
            this.renderer = new Renderer(height, width);

            this.keyStrokeSound = new SoundPlayer(Engine.KeyStrokeSoundPath_Test);
            this.bossFightSound = new SoundPlayer(Engine.BossFightSoundPath_Test);

            var bossReader = new BlockReader(Engine.BossTextFilePath_Test);
            var textReader = new WordReader(Engine.WordTextFilePath_Test);

            this.generator = new WordGenerator(textReader.GetWordDataBase(), this.width);
            
            this.selectedObject = null;
            this.inRefreshingSemaphore = false;
        }

        // TODO: Insert BossFight Mode 
        public void Start()
        {
            keyboard.LetterPressed += (sender, eventInfo) =>
            {
                this.OnLetterPressed((sender as Keyboard).pressedKey.KeyChar);
            };

            Timer timer = new Timer(TimerCallback, null, 0, 500);

            while (true)
            {
                this.keyboard.ProcessInput();
            }
        }

        #region OnKeyPressed

        // TODO: Merge OnSpace with OnLetter Pressed methods
        public void OnSpacePressed()
        {
            if (Program.SoundOn)
            {
                this.keyStrokeSound.Play();
            }

            if (selectedObject != null)
            {
                var notGuessed = this.selectedObject.FirstNotGuessed();
                if (WordManipulator.IsCurrentSpace(notGuessed))
                {
                    WordManipulator.ChangeFirstSpace(notGuessed);
                }
                else
                {
                    WordManipulator.ToLower(notGuessed);
                    this.selectedObject = null;
                }
            }
        }
        
        public void OnLetterPressed(char pressedChar)
        {
            if (Program.SoundOn)
            {
                keyStrokeSound.Play();
            }

            if (this.selectedObject == null)
            {
                if (this.SelectWord(pressedChar))
                {
                    WordManipulator.MakeFirstLowerToUpperChar(this.selectedObject.FirstNotGuessed());
                    this.RefreshScreen();
                }
            }
            else
            {
                var firstNotGuessed = selectedObject.FirstNotGuessed();
                if (firstNotGuessed == null)
                {
                    return;
                }

                if (WordManipulator.MatchFirstLowerChar(firstNotGuessed, pressedChar))
                {
                    WordManipulator.MakeFirstLowerToUpperChar(firstNotGuessed);
                }
                else
                {
                    WordManipulator.ToLower(firstNotGuessed);
                    this.selectedObject = null;
                }

                this.RefreshScreen();
            }
        }
        #endregion OnKeyPressed

        private void AddText(GameObject obj)
        {
            this.activeObjects.Add(obj);
        }

        private bool SelectWord(char inputChar)
        {
            this.selectedObject = this.activeObjects.FirstOrDefault(x =>
                WordManipulator.MatchFirstLowerChar(x.FirstNotGuessed(), inputChar));

            return this.selectedObject != null;
        }

        private void TimerCallback(Object o)
        {
            this.MoveActiveObjects();
            //TODO : Adding new Word
            this.AddText(this.generator.NextSingleLineText());
            this.RefreshScreen();
        }

        #region ForEach

        private void UpdateObjects()
        {
            foreach (var word in this.activeObjects)
            {
                word.Update();
            }
        }

        private void RendererReady()
        {
            foreach (var item in this.activeObjects)
            {
                this.renderer.InsertIntoField(item);
            }
        }

        private void MoveActiveObjects()
        {
            foreach (var textField in this.activeObjects)
            {
                textField.Move();
            }
        }

        private void RemoveGuessed()
        {
            if (this.selectedObject != null && this.selectedObject.Destroy)
            {
                this.selectedObject = null;
            }

            foreach (var gameObject in this.activeObjects)
            {
                gameObject.Update();
            }

            if (this.activeObjects.Count > 0)
            {
                var lowestWord = this.activeObjects[0];
                if (lowestWord.TopLeft.X > this.height - 2)
                {
                    lowestWord.Destroy = true;
                }
            }

            this.activeObjects.RemoveAll(x => x.Destroy);
        }

        #endregion ForEach

        private void BossScreen()
        {
            Console.WriteLine("\n\n\n\n\n\n\n\n");
            Console.WriteLine("\t" + @"  ____   ____   _____ _____   ______ _____ _____ _    _ _______ ");
            Console.WriteLine("\t" + @" |  _ \ / __ \ / ____/ ____| |  ____|_   _/ ____| |  | |__   __|");
            Console.WriteLine("\t" + @" | |_) | |  | | (___| (___   | |__    | || |  __| |__| |  | |   ");
            Console.WriteLine("\t" + @" |  _ <| |  | |\___ \\___ \  |  __|   | || | |_ |  __  |  | |   ");
            Console.WriteLine("\t" + @" | |_) | |__| |____) |___) | | |     _| || |__| | |  | |  | |   ");
            Console.WriteLine("\t" + @" |____/ \____/ \____/\____/  |_|    |_____\_____|_|  |_|  |_|   ");
        }

        private void RefreshScreen()
        {
            // OnKeyPress and TimerCallback call this function from different threads.
            // If not handled like this, strange things occure
            if (!this.inRefreshingSemaphore)
            {
                this.inRefreshingSemaphore = true;
                this.RemoveGuessed();
                this.renderer.ClearBuffer();
                this.RendererReady();
                this.renderer.FlushBuffer();
                this.inRefreshingSemaphore = false;
            }
        }
    }
}